package whf.framework.log.util;

import java.sql.Timestamp;
import java.util.Iterator;
import java.util.List;

import whf.framework.entity.Entity;
import whf.framework.log.Log;
import whf.framework.log.LogFactory;
import whf.framework.log.entity.AuditHistory;
import whf.framework.log.entity.AuditHistoryDetail;
import whf.framework.log.service.AuditHistoryDetailServiceImp;
import whf.framework.log.service.AuditHistoryService;
import whf.framework.log.service.AuditHistoryServiceImp;
import whf.framework.meta.MetaManager;
import whf.framework.resource.thread.AbstractPoolableThread;
import whf.framework.resource.thread.ThreadPool;
import whf.framework.security.UserContext;
import whf.framework.util.DateUtils;
import whf.framework.util.PropertyChangeUtils;
import whf.framework.util.ThreadContext;
import whf.framework.util.Utils;

/**
 * @author king
 * @create 2008-1-21 下午10:55:22
 * 
 */
public class AuditUtils {
	private static Log log = LogFactory.getLog(AuditUtils.class);
	
	private final static int TYPE_CREATE = 1;
	private final static int TYPE_UPDATE = 2;
	private final static int TYPE_DELETE = 3;
	
	private static AuditUtils instance = new AuditUtils();
	private List<AuditItem<? extends Entity>> auditItems = Utils.newArrayList();
	
	private AuditHistoryService service;
	
	private AuditUtils() {
		AuditThread thread = new AuditThread();
		ThreadPool.getDefaultInstance().addThread(thread);
	}
	
	public static AuditUtils getInstance() {
		return instance;
	}
	
	private AuditHistoryService getService() throws Exception{
		if(service != null) return service;
		service = AuditHistoryServiceImp.getAuditHistoryService();
		return service;
	}
	
	public <T extends Entity> void logCreate(UserContext uc, T newValue) {
		if(newValue instanceof AuditHistory) return;
		if(!ThreadContext.isAuditableInThread()) return;
		if(uc == null) uc = ThreadContext.getUserContext();
		addAuditItem(new AuditItem<T>(uc, newValue, null, TYPE_CREATE));
	}
	
	public <T extends Entity> void logUpdate(UserContext uc, T newValue, T oldValue) {
		if(newValue instanceof AuditHistory) return;
		if(!ThreadContext.isAuditableInThread()) return;
		if(uc == null) uc = ThreadContext.getUserContext();
		addAuditItem(new AuditItem<T>(uc, newValue, oldValue, TYPE_UPDATE));
	}
	
	public <T extends Entity> void logDelete(UserContext uc, T oldValue) {
		if(oldValue instanceof AuditHistory) return;
		if(!ThreadContext.isAuditableInThread()) return;
		if(uc == null) uc = ThreadContext.getUserContext();
		addAuditItem(new AuditItem<T>(uc, null, oldValue, TYPE_DELETE));
	}
	
	private <T extends Entity> void addAuditItem(AuditItem<T> item) {
		if(!instance.auditItems.contains(item))
			instance.auditItems.add(item);
	}
	
	class AuditItem<T> {
		private UserContext op;
		private UserContext uc;
		private T oldValue;
		private T newValue;
		private long now;
		private int type;
		
		AuditItem(UserContext uc, T newValue, T oldValue, int type) {
			this.op = ThreadContext.getUserContext();
			this.uc = uc;
			this.oldValue = oldValue;
			this.newValue = newValue;
			this.type = type;
			this.now = System.currentTimeMillis();
		}
		
		public int hashCode() {
			if(oldValue != null) {
				return oldValue.hashCode();
			} else if(newValue != null) {
				return newValue.hashCode();
			} else {
				return super.hashCode();
			}
		}
	}
	
	class AuditThread extends AbstractPoolableThread {
		
		/* (non-Javadoc)
		 * @see whf.framework.resource.thread.AbstractPoolableThread#isTimeout()
		 */
		@Override
		public boolean isTimeout() {
			return false;
		}

		/* (non-Javadoc)
		 * @see whf.framework.resource.thread.AbstractHandle#handleWhenTimeout()
		 */
		@Override
		public void run() {
			while(true) {
				List<AuditItem<? extends Entity>> items = Utils.newArrayList();
				synchronized(auditItems) {
					items.addAll(auditItems);
					auditItems.clear();
				}
				for(Iterator<AuditItem<? extends Entity>> it = items.iterator(); it.hasNext(); ) {
					AuditItem<? extends Entity> item = it.next();
					try{
						whf.framework.meta.entity.Object meta = MetaManager.getDefaultInstance().findByEntityClass(item.newValue.getClass().getName());
						if(!meta.isAuditable()) return;
						ThreadContext.setUserContext(item.op);
						
						AuditHistory audit = new AuditHistory();
						audit.setCreateBy(item.op == null? null: item.op.getUser());
						audit.setCreateDate(DateUtils.getNow());
						//
						audit.setWho(item.uc == null? null: item.uc.getUser() == null? null: item.uc.getUser().getUsername());
						audit.setWhen(new Timestamp(item.now));
						audit.setWhatType(item.newValue.getClass().getName());
						audit.setWhat(item.newValue.getId());
						
						List<AuditHistoryDetail> details = null;
						if(LogConfig.isAuditHistoryDetail())
							details = PropertyChangeUtils.getPropertyChangeLog(meta, item.oldValue, item.newValue);
						
						if(item.type == TYPE_UPDATE) {
							audit.setName("UPDATE");
							
							StringBuilder sb = new StringBuilder();
							for(AuditHistoryDetail detail: details) {
								sb.append("属性‘"+detail.getName() + "’ 从 ‘" + detail.getOldValue() + "’修改为‘" + detail.getNewValue() + "’;<br> ");
							}
							audit.setRemarks(sb.toString());
						} else if(item.type == TYPE_CREATE) {
							audit.setName("CREATE");
						} else if(item.type == TYPE_DELETE) {
							audit.setName("DELETE");
						}

						getService().create(audit);
						if(LogConfig.isAuditHistoryDetail() && details != null)
							for(AuditHistoryDetail detail: details) {
								detail.setAuditHistory(audit);
								AuditHistoryDetailServiceImp.getAuditHistoryDetailService().create(detail);
							}
					} catch(Exception e){
						log.error(this, e);
					}
					//
					it.remove();
				}	// end for
				try{
					Thread.sleep(1000 * 10);
				} catch(Exception e){
					// ignore
				}
			}		// end while
		}	// end run
	}
}
